#encoding=utf8
'''
Created on Aug 10, 2015

@author: jingyang <jingyang@smalltreemedia.com>
'''
from django.contrib import admin, messages
from qa_info.models import DevQAInfo, DevQAStatusRecord, DevQANote,\
    ReleaseQAInfo, ReleaseQAStatusRecord, ReleaseQANote
from utils import field, plaintext2html, api_call
from django.utils import timezone
from qa_info.forms import DevQAInfoForm, DevQANoteInlineForm, ReleaseQAInfoForm
from django.contrib.admin.filters import SimpleListFilter
from app_info.models import ProductionTeam, AppVersion
from sub_info.models import SubInfo
import settings
import json
from django.core.mail import mail_admins


class BaseQAStatusRecordInline(admin.TabularInline):

    can_delete = False
    extra = 0
    suit_classes = 'suit-tab suit-tab-status_record'
    readonly_fields = ('status_before', 'status_after', 'editor', 'timestamp')


class BaseQANoteInline(admin.TabularInline):

    can_delete = False
    extra = 0
    form = DevQANoteInlineForm
    suit_classes = 'suit-tab suit-tab-progress'
    readonly_fields = ('editor', 'timestamp')


class DevQAStatusRecordInline(BaseQAStatusRecordInline):

    model = DevQAStatusRecord

    def get_formset(self, request, obj=None, **kwargs):
        if obj:
            self.max_num = obj.devqastatusrecord_set.count()
        return admin.TabularInline.get_formset(self, request, obj=obj, **kwargs)


class DevQANoteInline(BaseQANoteInline):

    model = DevQANote


class PJMListFilter(SimpleListFilter):

    title = 'PJM'
    parameter_name = 'pjm'

    def lookups(self, request, model_admin):
        return ProductionTeam.objects.values_list('user', 'user__username')\
            .order_by('user__username').distinct()

    def queryset(self, request, queryset):
        if self.value():
            app_list = AppVersion.objects.filter(owner=self.value())\
                .values_list('app', 'code').distinct()
            data_list = []
            for al in app_list:
                data_list += queryset.filter(app=al[0], version=al[1])
            data_list = list(set([d.pk for d in data_list]))
            return queryset.filter(id__in=data_list)
        return queryset


class ProgrammerListFilter(SimpleListFilter):

    title = 'Programmer'
    parameter_name = 'programmer'

    def lookups(self, request, model_admin):
        p_list = AppVersion.objects.values_list('programmer', flat=True).distinct()
        rtn = list(set(reduce(lambda x, y: x + y, [x.split(',') for x in p_list if x])))
        return sorted([(i.split(' - ')[0], i) for i in rtn])

    def queryset(self, request, queryset):
        if self.value():
            app_list = AppVersion.objects.filter(programmer__contains=self.value())\
                .values_list('app', 'code').distinct()
            data_list = []
            for al in app_list:
                data_list += queryset.filter(app=al[0], version=al[1])
            data_list = list(set([d.pk for d in data_list]))
            return queryset.filter(id__in=data_list)
        return queryset


class TesterListFilter(SimpleListFilter):

    title = 'Tester'
    parameter_name = 'tester'

    def lookups(self, request, model_admin):
        t_list = model_admin.model.objects.values_list('tester', flat=True).distinct()
        if not t_list or t_list[0] is None:
            return ()
        rtn = list(set(reduce(lambda x, y: x + y, [i.split(',') for i in t_list if i])))
        return sorted([(n.split(' - ')[0], n) for n in rtn])

    def queryset(self, request, queryset):
        if self.value():
            return queryset.filter(tester__contains=self.value())
        return queryset


class DevQAInfoAdmin(admin.ModelAdmin):
    list_per_page = 20
    list_display = ('request_id', 'get_app', 'version', 'get_pjm',
        'get_programmer', 'create_time', 'get_tester',
        'status', 'real_clear_at', 'days_total')
    list_filter = ('app__app_store', 'status', PJMListFilter, ProgrammerListFilter,
        TesterListFilter)
    search_fields = ('request_id', 'app__prj_code')
    raw_id_fields = ('app',)
    form = DevQAInfoForm
    add_form_template = 'dev_qainfo_add_form.html'
    add_info_fields = (None, {'classes': ('suit-tab', 'suit-tab-basic'),
        'fields': ('app', 'version', 'code_dir')})
    view_dev_info_fields = (None, {'classes': ('suit-tab', 'suit-tab-basic'),
        'fields': ('request_id', 'editor', 'create_time', 'get_app', 'version',
        'get_code_dir', 'get_prd_type', 'get_app_store',
        'get_studio', 'get_prd_purpose',
        ('get_has_ads', 'get_ads_requests'),
        ('get_has_cross_promo', 'get_cross_promo_requests'),
        ('get_has_iap', 'get_iap_requests'),
        'get_pjm', 'get_programmer', 'get_sdk_required')})
    assign_tester_fields = ('Assign to Tester', {'classes': ('suit-tab', 'suit-tab-basic'),
        'fields': ('tester',)})
    progress_fields = ('Progress', {'classes': ('suit-tab', 'suit-tab-progress'),
        'fields': ('status', 'bug_addr', 'real_clear_at', 'bugs_count',
        'time_spent', 'days_pending', 'days_qa', 'days_total')})
    record_fields = (None, {'classes': ('suit-tab', 'suit-tab-status_record'),
        'fields': ('status_before', 'status_after', 'editor', 'timestamp')})

    @field('SDK in Use', allow_tags=True)
    def get_sdk_required(self, obj):
        v = obj.get_app_version()
        if v:
            return '<br />'.join(map(unicode, v.get_sdk_required_display()))
        return ''

    @field('Ads Requests', allow_tags=True)
    def get_ads_requests(self, obj):
        return "<br />".join(["<a href='/admin/ads_info/adsinfo/%s' target='_blank'>%s</a> - %s" %\
                              (i.pk, i.request_id, i.get_status_display())\
                              for i in obj.app.adsinfo_set.all()])

    @field('Cross Promo Ads Requests', allow_tags=True)
    def get_cross_promo_requests(self, obj):
        return "<br />".join(["<a href='/admin/ads_info/adsinfo/%s' target='_blank'>%s</a> - %s" %\
                              (i.pk, i.request_id, i.get_status_display())\
                               for i in obj.app.adsinfo_set.filter(cross_promo='Y')])

    @field('IAP Requests', allow_tags=True)
    def get_iap_requests(self, obj):
        return "<br />".join(["<a href='/admin/iap_info/iapinfo/%s' target='_blank'>%s</a> - %s" %\
                              (i.pk, i.request_id, i.get_status_display())\
                              for i in obj.app.iapinfo_set.all()])

    @field('Tester', allow_tags=True)
    def get_tester(self, obj):
        if not obj.tester:
            return ''
        return "<br />".join(obj.tester.split(','))

    @field('App', allow_tags=True)
    def get_app(self, obj):
        return "<a href='/admin/app_info/app/%s/' target='_blank'>%s</a>" %\
            (obj.app_id, obj.app)

    @field('Code Directory', allow_tags=True)
    def get_code_dir(self, obj):
        return plaintext2html(obj.code_dir)

    @field('Production Type')
    def get_prd_type(self, obj):
        v = obj.get_app_version()
        return v.get_type_display() if v else ''

    @field('App Store', admin_order_field='app__app_store')
    def get_app_store(self, obj):
        return obj.app.app_store

    @field('Studio', admin_order_field='app__studio')
    def get_studio(self, obj):
        return obj.app.studio

    @field('Production Purpose', allow_tags=True)
    def get_prd_purpose(self, obj):
        v = obj.get_app_version()
        if not v:
            return ''
        return "<br />".join(v.get_purpose().split(','))

    @field('Has Ads?', boolean=True)
    def get_has_ads(self, obj):
        v = obj.get_app_version()
        return v.ads_required == 'Y' if v else False

    @field('Has Cross Promo?', boolean=True)
    def get_has_cross_promo(self, obj):
        v = obj.get_app_version()
        return v.cross_promo_required == 'Y' if v else False

    @field('Has IAP?', boolean=True)
    def get_has_iap(self, obj):
        v = obj.get_app_version()
        return v.iap_required == 'Y' if v else False

    @field('PJM')
    def get_pjm(self, obj):
        v = obj.get_app_version()
        return v.get_coordinator() if v else ''

    @field('Programmer', allow_tags=True)
    def get_programmer(self, obj):
        v = obj.get_app_version()
        if not v:
            return ""
        return "<br />".join(v.programmer.split(',')) if v.programmer else ""

    def get_sub_req(self, obj, validation=[]):
        qs = SubInfo.objects.filter(app=obj.app, version=obj.version)
        if validation:
            qs = qs.filter(validation__in=validation)
        return qs

    def get_inline_instances(self, request, obj=None):
        inlines = []
        if obj:
            self.inlines = inlines + [DevQAStatusRecordInline, DevQANoteInline]
        else:
            self.inlines = inlines
        return admin.ModelAdmin.get_inline_instances(self, request, obj=obj)

    def get_actions(self, request):
        """
        remove delete action
        """
        actions = admin.ModelAdmin.get_actions(self, request)
        if actions.get('delete_selected'):
            del actions['delete_selected']
        return actions

    def get_fieldsets(self, request, obj=None):
        if not obj:
            return (self.add_info_fields,)
        else:
            if not obj.tester:
                return (self.view_dev_info_fields, self.assign_tester_fields)
            return (self.view_dev_info_fields, self.assign_tester_fields,
                    self.progress_fields)

    def get_readonly_fields(self, request, obj=None):
        base = ('request_id', 'editor', 'create_time',
            'days_pending', 'days_qa', 'days_total',
            'get_has_ads', 'get_has_cross_promo', 'get_has_iap',
            'get_ads_requests', 'get_cross_promo_requests', 'get_iap_requests')
        if obj:
            base += self.view_dev_info_fields[1]['fields']
            base += ('code_dir', 'app')
            if not obj.tester:
                base += self.progress_fields[1]['fields']
                self.suit_form_tabs = (('basic', 'Request Info'),)
            else:
                self.suit_form_tabs = (('basic', 'Request Info'),
                                       ('progress', 'Progress'),
                                       ('status_record', 'Status Change Log'))
        else:
            self.suit_form_tabs = (('basic', 'Request Info'),)
        return base

    def save_formset(self, request, form, formset, change):
        formset.save()
        if formset.model == DevQANote:
            changed_notes = formset.save(commit=False)
            for n in changed_notes:
                n.editor = request.user.email
                n.save()

    def response_add(self, request, obj, post_url_continue=None):
        if '_addanother' not in request.POST and '_popup' not in request.POST:
            request.POST['_continue'] = 1
        return admin.ModelAdmin.response_add(self, request, obj, post_url_continue=post_url_continue)

    def queryset(self, request):
        qs = admin.ModelAdmin.queryset(self, request)
        qs_ids = qs.values_list('pk', flat=True)
        pending = qs_ids.filter(status='P')
        progress = qs_ids.filter(status='I')
        cleared = qs_ids.filter(status='D')
        hold = qs_ids.filter(status='O')
        cancel = qs_ids.filter(status='X')
        data_list = reduce(lambda x, y: x + y, map(list, [pending, progress, cleared, hold, cancel]))
        clauses = ' '.join(['WHEN %s.id=%s THEN %s' % (self.opts.db_table, pk, i) for i, pk in enumerate(data_list)])
        ordering = 'CASE %s END' % clauses
        qs = qs.filter(id__in=data_list).extra(select={'ordering': ordering}).order_by('ordering',)
        return qs

    def save_model(self, request, obj, form, change):
        now = timezone.now()
        if not change:
            obj.request_id = 'RQA1-%s%02i%02i%02i' % (now.year, now.month,
                now.day, DevQAInfo.objects.get_latest_num())
            obj.editor = request.user.email
            obj.status = 'P'
        else:
            if 'status' in form.changed_data:
                status_before = self.model.objects.get(id=obj.id).status
                status_after = obj.status
                DevQAStatusRecord.objects.create(dev_qa_info=obj,
                    status_before=status_before, status_after=status_after,
                    editor=request.user.email)
                if obj.status in ['P', 'X']:
                    obj.days_pending, obj.days_qa, obj.days_total = None, None, None
                elif obj.status == 'I':
                    obj.days_pending = round((now - obj.get_last_pending_time()).total_seconds() / 3600 / 24, 1)
                elif obj.status == 'D':
                    # when dev qa cleared, write code_dir for the version
                    v = obj.get_app_version()
                    if v and not v.code_dir:
                        v.code_dir = obj.code_dir
                        v.save()
                    last_progress_time = obj.get_last_progress_time()
                    if last_progress_time and obj.days_pending:
                        obj.days_qa = round((now - last_progress_time).total_seconds() / 3600 / 24, 1)
                        obj.days_total = obj.days_pending + obj.days_qa
                    # if the Submission Request of this app-version valid, make Release QA Request
                    sub_info = self.get_sub_req(obj, ['V'])[:1]
                    if sub_info.exists():
                        if not ReleaseQAInfo.objects.filter(dev_qa=obj,
                            sub_info=sub_info[0], status__in=['P', 'I', 'D', 'O']).exists():
                            ReleaseQAInfo.objects.create(dev_qa=obj, sub_info=sub_info[0],
                                editor=sub_info[0].editor)
                            rtn = api_call('%s%s' % (settings.PS_DOMAIN, settings.PS_CREATE_META),
                                json.dumps({'request': {'request_id': sub_info[0].request_id}}),
                                    'POST', {'Content-Type': 'application/json'})
                            if not rtn.get('code') == 1:
                                mail_admins('Meta Request Sent Failed', rtn.get('msg'))
                                self.message_user(request, 'Server Error', messages.ERROR)
                                return
        obj.save()


class ReleaseQAStatusRecordInline(BaseQAStatusRecordInline):

    model = ReleaseQAStatusRecord

    def get_formset(self, request, obj=None, **kwargs):
        if obj:
            self.max_num = obj.releaseqastatusrecord_set.count()
        return admin.TabularInline.get_formset(self, request, obj=obj, **kwargs)


class ReleaseQANoteInline(BaseQANoteInline):

    model = ReleaseQANote


class ReleaseQAInfoAdmin(admin.ModelAdmin):
    list_per_page = 20
    list_display = ('request_id', 'get_app', 'get_version', 'get_programmer',
        'create_time', 'status', 'get_tester', 'get_dev_qa', 'get_sub_info')
    list_filter = ('dev_qa__app__app_store', 'status', TesterListFilter)
    search_fields = ('request_id', 'tester', 'dev_qa__request_id',
        'sub_info__request_id', 'dev_qa__app__prj_code')
    form = ReleaseQAInfoForm
    inlines = [ReleaseQAStatusRecordInline, ReleaseQANoteInline]
    suit_form_tabs = (('basic', 'Request Info'), ('progress', 'Progress'),
        ('status_record', 'Status Change Log'))
    view_info_fields = (None, {'classes': ('suit-tab', 'suit-tab-basic'),
        'fields': ('request_id', 'editor', 'create_time',
        'get_app', 'get_version', 'get_prd_type', 'get_app_store', 'get_studio',
        'get_prd_purpose',
        ('get_has_ads', 'get_ads_requests'),
        ('get_has_cross_promo', 'get_cross_promo_requests'),
        ('get_has_iap', 'get_iap_requests'),
        'get_sub_info', 'get_is_resub', 'get_programmer', 'get_edqa',
        'get_testing_notes')})
    view_dev_qa_fields = ('Dev QA', {'classes': ('suit-tab', 'suit-tab-basic'),
        'fields': ('get_dev_qa', 'get_dev_qa_tester', 'get_dev_qa_status',
        'get_dev_qa_cleared_date', 'get_dev_qa_code_dir', 'get_dev_qa_bug_addr')})
    progress_fields = (None, {'classes': ('suit-tab', 'suit-tab-progress'),
        'fields': ('status', 'tester', 'real_clear_at', 'bugs_count', 'time_spent')})
    checklist_fields = ('Check List', {'classes': ('suit-tab', 'suit-tab-progress'),
        'fields': ('ad_sdk', 'analytics_sdk', 'newsblast_sdk', 'studio_sdk',
        'about_page', 'version_checker', 'checked_by', 'other')})
    other_checklist_fields = ('Check List', {'classes': ('suit-tab', 'suit-tab-progress'),
        'fields': (('ad_sdk', 'analytics_sdk', 'newsblast_sdk', 'checked_by',
        'other'))})

    @field('Tester', allow_tags=True)
    def get_tester(self, obj):
        if not obj.tester:
            return ""
        return "<br />".join(obj.tester.split(','))

    @field('App', allow_tags=True, admin_order_field='dev_qa__app')
    def get_app(self, obj):
        app = obj.dev_qa.app
        return "<a href='/admin/app_info/app/%s/' target='_blank'>%s</a>" %\
            (app.id, app)

    @field('Version', admin_order_field='dev_qa__version')
    def get_version(self, obj):
        return obj.dev_qa.version

    @field('Production Type')
    def get_prd_type(self, obj):
        v = obj.dev_qa.get_app_version()
        return v.get_type_display() if v else ""

    @field('App Store')
    def get_app_store(self, obj):
        return obj.dev_qa.app.app_store

    @field('Studio')
    def get_studio(self, obj):
        return obj.dev_qa.app.studio

    @field('Production Purpose', allow_tags=True)
    def get_prd_purpose(self, obj):
        v = obj.dev_qa.get_app_version()
        if not v:
            return ''
        return "<br />".join(v.get_purpose().split(','))

    @field('Has Ads?', boolean=True)
    def get_has_ads(self, obj):
        v = obj.dev_qa.get_app_version()
        return v.ads_required == 'Y' if v else False

    @field('Ads Requests', allow_tags=True)
    def get_ads_requests(self, obj):
        return "<br />".join(["<a href='/admin/ads_info/adsinfo/%s' target='_blank'>%s</a> - %s" %\
                              (i.pk, i.request_id, i.get_status_display())\
                              for i in obj.dev_qa.app.adsinfo_set.all()])

    @field('Has Cross Promo?', boolean=True)
    def get_has_cross_promo(self, obj):
        v = obj.dev_qa.get_app_version()
        return v.cross_promo_required == 'Y' if v else False

    @field('Cross Promo Ads Requests', allow_tags=True)
    def get_cross_promo_requests(self, obj):
        return "<br />".join(["<a href='/admin/ads_info/adsinfo/%s' target='_blank'>%s</a> - %s" %\
                              (i.pk, i.request_id, i.get_status_display())\
                               for i in obj.dev_qa.app.adsinfo_set.filter(cross_promo='Y')])

    @field('Has IAP?', boolean=True)
    def get_has_iap(self, obj):
        v = obj.dev_qa.get_app_version()
        return v.iap_required == 'Y' if v else False

    @field('IAP Requests', allow_tags=True)
    def get_iap_requests(self, obj):
        return "<br />".join(["<a href='/admin/iap_info/iapinfo/%s' target='_blank'>%s</a> - %s" %\
                              (i.pk, i.request_id, i.get_status_display())\
                              for i in obj.dev_qa.app.iapinfo_set.all()])

    @field('Submission Request', allow_tags=True, admin_order_field='sub_info')
    def get_sub_info(self, obj):
        return "<a href='/admin/sub_info/subinfo/%s/' target='_blank'>%s</a>" %\
            (obj.sub_info_id, obj.sub_info)

    @field('Is Resub?', boolean=True)
    def get_is_resub(self, obj):
        return obj.sub_info.is_resub == 'Y'

    @field('Front-end Engineer', allow_tags=True)
    def get_programmer(self, obj):
        v = obj.dev_qa.get_app_version()
        if not v:
            return ""
        return "<br />".join(v.programmer.split(',')) if v.programmer else ""

    @field('Estimated Date of QA')
    def get_edqa(self, obj):
        return obj.sub_info.edqa

    @field('Testing Notes')
    def get_testing_notes(self, obj):
        return obj.sub_info.notes_to_qa

    @field('Dev QA Request', allow_tags=True, admin_order_field='dev_qa')
    def get_dev_qa(self, obj):
        return "<a href='/admin/qa_info/devqainfo/%s/' target='_blank'>%s</a>" %\
            (obj.dev_qa_id, obj.dev_qa)

    @field('Dev QA Tester', allow_tags=True)
    def get_dev_qa_tester(self, obj):
        return "<br />".join(obj.dev_qa.tester.split(','))

    @field('Dev QA Status')
    def get_dev_qa_status(self, obj):
        return obj.dev_qa.get_status_display()

    @field('Dev QA Cleared Date')
    def get_dev_qa_cleared_date(self, obj):
        return obj.dev_qa.real_clear_at

    @field('Dev QA Code Directory', allow_tags=True)
    def get_dev_qa_code_dir(self, obj):
        return plaintext2html(obj.dev_qa.code_dir)

    @field('Dev QA Bug Tracker Project Address', allow_tags=True)
    def get_dev_qa_bug_addr(self, obj):
        return plaintext2html(obj.dev_qa.bug_addr)

    def save_formset(self, request, form, formset, change):
        formset.save()
        if formset.model == ReleaseQANote:
            changed_notes = formset.save(commit=False)
            for n in changed_notes:
                n.editor = request.user.email
                n.save()

    def get_fieldsets(self, request, obj=None):
        check_list = self.checklist_fields
        if not self.get_app_store(obj) == 'iTunes App Store':
            check_list = self.other_checklist_fields
        return (self.view_info_fields, self.view_dev_qa_fields,
            self.progress_fields, check_list)

    def get_readonly_fields(self, request, obj=None):
        base = ('request_id', 'editor', 'create_time',
            'dev_qa', 'sub_info')
        base += self.view_dev_qa_fields[1]['fields']
        base += self.view_info_fields[1]['fields']
        base += ('get_has_ads', 'get_ads_requests',
                 'get_has_cross_promo', 'get_cross_promo_requests',
                 'get_has_iap', 'get_iap_requests')
        return base

    def get_actions(self, request):
        """
        remove delete action
        """
        actions = admin.ModelAdmin.get_actions(self, request)
        if actions.get('delete_selected'):
            del actions['delete_selected']
        return actions

    def has_add_permission(self, request):
        return False

    def queryset(self, request):
        qs = admin.ModelAdmin.queryset(self, request).order_by('-request_id')
        qs_ids = qs.values_list('pk', flat=True)
        pending = qs_ids.filter(status='P')
        progress = qs_ids.filter(status='I')
        cleared = qs_ids.filter(status='D')
        hold = qs_ids.filter(status='O')
        cancel = qs_ids.filter(status='X')
        data_list = reduce(lambda x, y: x + y, map(list, [pending, progress, cleared, hold, cancel]))
        clauses = ' '.join(['WHEN %s.id=%s THEN %s' % (self.opts.db_table, pk, i) for i, pk in enumerate(data_list)])
        ordering = 'CASE %s END' % clauses
        qs = qs.filter(id__in=data_list).extra(select={'ordering': ordering}).order_by('ordering',)
        return qs

    def save_model(self, request, obj, form, change):
        if 'status' in form.changed_data:
            status_before = self.model.objects.get(id=obj.id).status
            status_after = obj.status
            ReleaseQAStatusRecord.objects.create(release_qa_info=obj,
                status_before=status_before, status_after=status_after,
                editor=request.user.email)
        obj.save()


admin.site.register(DevQAInfo, DevQAInfoAdmin)
admin.site.register(ReleaseQAInfo, ReleaseQAInfoAdmin)
